# vim:ts=4:sw=4:noet:
"""
Smalltalk-Python (Spy) Objects. This is the main representation of
Smalltalk objects in the environment. 

SpyObjects are the core representation of smalltalk objects in the environment. The image files are serialised (pickled) versions of SpyObjects. A SpyObject can also emit the CMG version of itself (the binary format used by CVM).
"""

import logging
import mem

STATE_COMPILED   = 0
STATE_UNCOMPILED = 1
STATE_ERROR      = 2

################################################################################
class SpyObject:
	""" Base Spy class """

	def __init__(self, mem, cls, size=0):
		self.mem = mem
		self.cls = cls
		self.size = size

	def writeCMG(self, cmg):
		if self in cmg.objhash:
			return cmg.objhash[self]<<2
		r = self._writeCMG(cmg)
		return r

	def __getstate__(self):
		odict = self.__dict__.copy()
		del odict['mem']
		return odict

	def __setstate__(self, dict):
		self.__dict__.update(dict)
		self.mem = mem.Memory._instance

	def _writeCMG(self, cmg):
		mobj = cmg.newObject(self.size)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)

		for i in range(self.size):
			cmg.img[mobj+2+i] = self.mem.nilObject.writeCMG(cmg)
		return mobj<<2
	
	def fileOut(self, rootdir):
		pass
	
	def asString(self):
		return self.__repr__()

################################################################################
class SpyBinObject(SpyObject):
	""" Base Binary Object Spy Class """

	def __init__(self, mem, cls, size=0):
		SpyObject.__init__(self, mem, cls, size)

	def _writeCMG(self, cmg):
		mobj = cmg.newObject(self.size, True)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)
				
		data = []
		for d in self.data:
			data.append(d)
		if len(data)%2 == 1:
			data.append(0xFF)
		for i in range(len(data)/2):
			cmg.img[mobj+2+i] = (data[i*2 +1]<<8) + data[i*2]
		return mobj<<2

	def __len__(self):
		return len(self.data)

	def __repr__(self):
		return "<SpyByteArray %d bytes>" % len(self.data)

################################################################################
class SpyClass(SpyObject):
	""" Spy Class """

	def __init__(self, mem, name, parent=None):
		SpyObject.__init__(self, mem, None, 4)
		self.name = name
		self.parent = parent
		self.methods = None
		self.instanceSize = 0
		self.instanceNames = []
		self.docs = ""
		self.pragmas = {}
		if parent:
			self.instanceSize = parent.instanceSize 

	def newClassInit(self):
		self.methods = SpyDictionary(self.mem)
		self.cls = SpyClass(self.mem, "Meta"+self.name, self.parent.cls)
		self.cls.methods = SpyDictionary(self.mem)
		self.cls.cls = self.mem.getGlobal("Class")
		self.cls.addPragma("category", "")
		self.addPragma("category","")

	def addPragma(self, k,v):
		self.pragmas[k] = v

	def getPragma(self,k):
		if k in self.pragmas:
			return self.pragmas[k]
		return ""
			
	def inLibrary(self):
		return self.getPragma("category")[:7]=="Library"

	def getAllIvars(self, withClasses=False):
		ivars = []
		curcls = self
		while curcls:
			for i,iv in enumerate(curcls.instanceNames):
				if withClasses:
					ivars.insert(i,(curcls.name,iv))
				else:
					ivars.insert(i,iv)
			if curcls.parent != self.mem.nilObject:
				curcls = curcls.parent
			else:
				break
		return ivars

	def addIvar(self, ivar):
		self.instanceNames.append(ivar)
		#self.instanceSize += 1
		logging.warn("addIvar: add ivar to child classes")

	def removeIvar(self, ivar):
		logging.info("removing %s from %s" % (ivar, self.instanceNames))
		self.instanceNames.remove(ivar)
		#self.instanceSize -= 1
		logging.warn("removeIvar: recompile all methods")

	def hasMethod(self, name):
		return self.methods.has(name)

	def removeMethod(self, name):
		self.methods.remove(name)

	def addMethod(self, name, meth):
		self.methods.add(name, meth)

	def getMethod(self, name, inherit=True):
		if self.hasMethod(name):
			return self.methods.get(name)
		elif inherit and (self.parent != self.mem.nilObject):
			return self.parent.getMethod(name)
		else:
			return None

	def _writeCMG(self, cmg):
		mobj = cmg.newObject(self.size)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)

		cmg.img[mobj+2] = self.mem.newSymbol(self.name).writeCMG(cmg) # 0 - name
		cmg.img[mobj+3] = self.parent.writeCMG(cmg) # 1 - parent
		cmg.img[mobj+4] = self.methods.writeCMG(cmg) # 2 - methods
		cmg.img[mobj+5] = cmg.newInteger(len(self.getAllIvars())) # 3 - instance size
		#if self.instanceSize != len(self.getAllIvars()):
		#	raise Exception("bad ivarsize in %s" % self.name)

		for i in range(self.size-4):
			cmg.img[mobj+6+i] = self.mem.nilObject.writeCMG(cmg)
		return mobj<<2

	def fileOutClass(self, rootdir, f):
		name = self.name
		metaname = self.cls.name
		metasupername = self.cls.parent.name
		if self.parent == self.mem.nilObject:
			supername = "nil"
		else:
			supername = self.parent.name
		if "docs" in dir(self):
			print >> f, """\"! %s\"""" % self.docs.replace("\"", "\\\"")
		print >> f, "%s subclass: %s [" % (supername, name)
		if len(self.instanceNames)>0:
			print >> f, "|",
			for v in self.instanceNames:
				print >> f, v,
			print >> f, "|"
		for p in self.pragmas:
			print >> f, "<%s: '%s'>" % (p, self.pragmas[p])
		mnames = self.methods.pdict.keys()
		mnames.sort()
		for mn in mnames:
			self.methods.pdict[mn].fileOut(f)
		print >> f, "]"
		print >> f, ""

	def fileOut(self, rootdir):
		if self.name[:4] == "Meta":
			return
		#print "--- fileout",rootdir
		f = file(rootdir+"/"+self.name+".st","wb")
		self.fileOutClass(rootdir, f)
		self.cls.fileOutClass(rootdir, f)
		f.close()

	def __repr__(self):
		return "<SpyClass %s 0x%X>" % (self.name, id(self))
	
	def asString(self):
		return "<Class %s>" % self.name

################################################################################
class SpyArray(SpyObject):
	""" Spy Array """

	def __init__(self, mem, init):
		SpyObject.__init__(self, mem, mem.getGlobal("Array"), len(init))
		self.parr = init

	def add(self, n, v):
		self.parr[n] = v
	
	def _writeCMG(self, cmg):
		mobj = cmg.newObject(self.size)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)

		for i,d in enumerate(self.parr):
			cmg.img[mobj+2+i] = d.writeCMG(cmg)
		return mobj<<2
	
	def __repr__(self):
		return "<SpyArray %d items>" % len(self.parr)

	def asString(self):
		r = "#("
		for i in self.parr:
			r+=" "+i.asString()
			if len(r)>16:
				r+=" ..."
				break
		r += " )"
		return r

################################################################################
class SpyOrderedArray(SpyObject):
	""" Spy OrderedArray """

	def __init__(self, mem, init):
		SpyObject.__init__(self, mem, mem.getGlobal("OrderedArray"), len(init))
		self.parr = init

	def add(self, n, v):
		self.parr[n] = v
	
	def _writeCMG(self, cmg):
		mobj = cmg.newObject(self.size)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)

		for i,d in enumerate(self.parr):
			cmg.img[mobj+2+i] = d.writeCMG(cmg)
		return mobj<<2

	def __repr__(self):
		return "<SpyOrderedArray %d items>" % len(self.parr)

################################################################################
class SpyByteArray(SpyBinObject):
	""" Spy ByteArray """

	def __init__(self, mem, data):
		SpyBinObject.__init__(self, mem, mem.getGlobal("ByteArray"), len(data))
		self.data = data

################################################################################
class SpyDictionary(SpyObject):
	""" Spy Dictionary """

	def __init__(self, mem):
		SpyObject.__init__(self, mem, mem.getGlobal("Dictionary"), 2)
		self.pdict = {}

	def remove(self, n):
		del self.pdict[n]

	def add(self, n, v):
		self.pdict[n] = v

	def get(self, n):
		return self.pdict[n]

	def has(self, n):
		return n in self.pdict

	def __len__(self):
		return len(self.pdict)
	
	def _writeCMG(self, cmg):
		mobj = cmg.newObject(self.size)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)

		keys = self.pdict.keys()
		keys.sort()
		vals = []
		for k in keys:
			vals.append(self.pdict[k])
		keys = map(lambda x: self.mem.newSymbol(x), keys)
		cmg.img[mobj+2] = SpyOrderedArray(self.mem, keys).writeCMG(cmg)
		cmg.img[mobj+3] = SpyArray(self.mem, vals).writeCMG(cmg)
		return mobj<<2

	def __repr__(self):
		return "<SpyDictionary %d items>" % len(self.pdict.keys())
	
	def asString(self):
		return "<Dictionary>"

################################################################################
class SpyMethod(SpyObject):
	""" Spy Method 

	A SpyMethod has dependants (methods which depend/call this method), and 
	reverse-dependants (methods which this method depends on/calls).
	Dependant/Reverse-Dependant mappings are updated when a method compiled.
	"""

	def __init__(self, mem, name, incls, source):
		SpyObject.__init__(self, mem, mem.getGlobal("Method"), 4)
		self.name = name
		self.incls = incls
		self.source = source
		self.sig = None
		self.args = []
		self.bclines = []
		self.docs = ""
		self.state = STATE_COMPILED
		self.dependants = {}
		self.revdependants = {}

	def clearDependants(self):
		""" Clear dependant map for this method """ 
		self.dependants = {}

	def clearRevDependants(self):
		""" Clear reverse dependant map for this method """
		self.revdependants = {}

	def removeDependant(self, depcls, depmeth):
		""" Remove a dependant """
		dep = "%s>>%s" % (depcls.name, depmeth.name)
		print "remove dep: %s" % dep
		if dep not in self.dependants:
			print "Warning: can't remove dep '%s' from %s" % (dep, self)
			return
		del self.dependants[dep]

	def addDependant(self, depcls, depmeth):
		""" Add a dependant (and update revdependant map of dependant) """
		dep = "%s>>%s" % (depcls.name, depmeth.name)
		revdep = "%s>>%s" % (self.name, self.incls.name)
		if dep not in self.dependants:
			self.dependants[dep] = (depcls, depmeth)
		depmeth.revdependants[revdep] = (self.incls, self)

	def getDependants(self):
		""" Get dependants """
		return self.dependants.values()
	
	def lineFromBp(self, bc):
		""" Get the source line number that maps to the specified bytecode """
		i = 0
		#print "lineFromBp in %s:0x%02X -" % (self.name, bc)
		while (i<len(self.bclines)) and (bc>=self.bclines[i][0]):
			i+= 1
		return self.bclines[i-1][1]

	def _writeCMG(self, cmg):
		#print "writing method (%d): %s" % (self.used, self.name)
		mobj = cmg.newObject(3+len(self.literals)) #self.size)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)

		cmg.img[mobj+2] = self.mem.newSymbol(self.name).writeCMG(cmg) # 0 - name
		cmg.img[mobj+3] = cmg.newInteger((self.maxStack<<6)|self.maxTemps) # 1 - tempsize
		cmg.img[mobj+4] = SpyByteArray(self.mem, self.bytecodes).writeCMG(cmg) # 2 - bytecodes
		for i,l in enumerate(self.literals):
			cmg.img[mobj+5+i] = l.writeCMG(cmg)
		return mobj<<2

	def fileOut(self, f):
		print >> f, """\"! %s\"""" % self.docs.replace("\"", "\\\"")
		print >> f, "%s [" % self.sig
		print >> f, "%s" % self.source,
		print >> f, "]"
		print >> f, ""

	def __repr__(self):
		return "<SpyMethod %s>" % self.name

################################################################################
class SpySymbol(SpyBinObject):
	""" Spy Symbol """

	def __init__(self, mem, name):
		SpyBinObject.__init__(self, mem, mem.getGlobal("Symbol"), len(name))
		self.name = name
		self.data = []
		for c in name:
			self.data.append(ord(c))
	
	def _writeCMG(self, cmg):
		self.optr = SpyBinObject._writeCMG(self, cmg)
		return self.optr

	def asString(self):
		return self.name

	def __repr__(self):
		return "<SpySymbol #%s>" % self.name
	
	def asString(self):
		return "#%s" % self.name

################################################################################
class SpyString(SpyBinObject):
	""" Spy String """

	def __init__(self, mem, value):
		SpyBinObject.__init__(self, mem, mem.getGlobal("String"), len(value))
		self.value = value
		self.data = []
		for c in value:
			self.data.append(ord(c))

	def __repr__(self):
		return "<SpyString '%s'>" % self.value

	def asString(self):
		return "'%s'" % self.value

################################################################################
class SpyInteger(SpyBinObject):
	""" Spy Integer """

	def __init__(self, mem, value):
		SpyBinObject.__init__(self, mem, mem.getGlobal("Integer"), 4)
		self.value = value
		self.data = []
		self.data.append((value    )&0xFF)
		self.data.append((value>>8 )&0xFF)
		self.data.append((value>>16)&0xFF)
		self.data.append((value>>24)&0xFF)

	def __repr__(self):
		return "<SpyInteger %d>" % self.value

	def __eq__(self, other):
		if not isinstance(other,SpyInteger):
			return False
		return self.value==other.value

	def __hash__(self):
		return hash(self.value)
	
	def asString(self):
		return "%d" % self.value

################################################################################
class SpySmallInt(SpyObject):
	""" Spy SmallInt """

	def __init__(self, mem, value):
		SpyObject.__init__(self, mem, mem.getGlobal("SmallInt"))
		self.value = value

	def _writeCMG(self, cmg):
		return cmg.newInteger(self.value)

	def __repr__(self):
		return "<SpySmallInt %d>" % self.value

	def __eq__(self, other):
		if not isinstance(other,SpySmallInt):
			return False
		return self.value==other.value

	def __hash__(self):
		return hash(self.value)
	
	def asString(self):
		return "%d" % self.value

################################################################################
class SpyChar(SpyObject):
	""" Spy Character """

	def __init__(self, mem, value):
		SpyObject.__init__(self, mem, mem.getGlobal("Char"), 1)
		self.value = value

	def _writeCMG(self, cmg):
		mobj = cmg.newObject(self.size)
		cmg.objhash[self] = mobj
		cmg.img[mobj+1] = self.cls.writeCMG(cmg)

		cmg.img[mobj+2] = cmg.newInteger(ord(self.value))
		return mobj<<2

	def asString(self):
		return "$%s" % self.value

